<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<style>
    body {
        background: linear-gradient(#1d463e, #000000);
    }
</style>
<body>
<canvas id="canvas"></canvas>
</body>
<script>
    let lightning = [];
    let lightTimeCurrent = 0;
    let lightTimeTotal = 50;

    function Lightning(c, cw, ch) {
        this.c = c;
        this.ctx = c.getContext('2d');
        this.cw = cw;
        this.ch = ch;
    }

    /**
     * 生成指定返回的随机数
     * @param rMi
     * @param rMa
     * @returns {number}
     */
    Lightning.prototype.rand = function (rMi, rMa) {
        return ~~((Math.random() * (rMa - rMi + 1)) + rMi);//相当于Math.floor 但是效率更高
    };

    Lightning.prototype.create = function (x, y, canSpawn) {
        lightning.push({
            x: x,
            y: y,
            xRange: this.rand(5, 30), //横左边的范围
            yRange: this.rand(5, 25), //纵坐标的范围
            path: [{ //闪电轨迹，起始点就是当前的路线起点
                x: x,
                y: y
            }],
            pathLimit: this.rand(10, 35), //控制闪电长度
            canSpawn: canSpawn,
            hasFired: false //闪电亮度
        });
    }

    Lightning.prototype.update = function () {
        var i = lightning.length;
        while (i--) {
            var light = lightning[i];

            light.path.push({
                x: light.path[light.path.length - 1].x + (this.rand(0, light.xRange) - (light.xRange / 2)),
                y: light.path[light.path.length - 1].y + (this.rand(0, light.yRange))
            });

            if (light.path.length > light.pathLimit) {
                lightning.splice(i, 1)
            }
            light.hasFired = true;
        }
    }

    Lightning.prototype.render = function () {
        var i = lightning.length;
        while (i--) {
            var light = lightning[i];

            this.ctx.strokeStyle = 'hsla(0, 100%, 100%, ' + this.rand(10, 100) / 100 + ')';
            this.ctx.lineWidth = 1;
            if (this.rand(0, 30) == 0) {
                this.ctx.lineWidth = 2;
            }
            if (this.rand(0, 60) == 0) {
                this.ctx.lineWidth = 3;
            }
            if (this.rand(0, 90) == 0) {
                this.ctx.lineWidth = 4;
            }
            if (this.rand(0, 120) == 0) {
                this.ctx.lineWidth = 5;
            }
            if (this.rand(0, 150) == 0) {
                this.ctx.lineWidth = 6;
            }

            this.ctx.beginPath();

            var pathCount = light.path.length;
            this.ctx.moveTo(light.x, light.y);
            for (var pc = 0; pc < pathCount; pc++) {

                this.ctx.lineTo(light.path[pc].x, light.path[pc].y);

                if (light.canSpawn) {
                    if (this.rand(0, 100) == 0) {
                        light.canSpawn = false;
                        this.create(light.path[pc].x, light.path[pc].y, false);//创建闪电分支
                    }
                }
            }

            if (!light.hasFired) {
                this.ctx.fillStyle = 'rgba(255, 255, 255, ' + this.rand(4, 12) / 100 + ')';
                this.ctx.fillRect(0, 0, this.cw, this.ch);
            }

            if (this.rand(0, 30) == 0) {
                this.ctx.fillStyle = 'rgba(255, 255, 255, ' + this.rand(1, 3) / 100 + ')';
                this.ctx.fillRect(0, 0, this.cw, this.ch);
            }

            this.ctx.stroke();
        }
    }

    Lightning.prototype.lightningTimer = function () {
        lightTimeCurrent++;
        if (lightTimeCurrent >= lightTimeTotal) {
            var newX = this.rand(100, this.cw - 100);
            var newY = this.rand(0, this.ch / 2);
            var createCount = this.rand(1, 3);//控制闪电数量
            while (createCount--) {
                this.create(newX, newY, true);
            }
            lightTimeCurrent = 0;
            lightTimeTotal = this.rand(30, 100);
        }
    }

    Lightning.prototype.clearCanvas = function () {
        this.ctx.globalCompositeOperation = 'destination-out';
        this.ctx.fillStyle = 'rgba(0,0,0,' + this.rand(1, 30) / 100 + ')';
        this.ctx.fillRect(0, 0, this.cw, this.ch);
        this.ctx.globalCompositeOperation = 'source-over';
    }


    window.onload = function () {

        var isCanvasSupported = function () {
            var elem = document.createElement('canvas');
            return !!(elem.getContext && elem.getContext('2d'));
        };

        /**
         * requestAnimationFrame 改写兼容
         */
        var setupRAF = function () {
            var lastTime = 0;
            var vendors = ['ms', 'moz', 'webkit', 'o'];
            for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
                window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
                window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
            }

            if (!window.requestAnimationFrame) {
                window.requestAnimationFrame = function (callback, element) {
                    var currTime = new Date().getTime();
                    var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                    var id = window.setTimeout(function () {
                        callback(currTime + timeToCall);
                    }, timeToCall);
                    lastTime = currTime + timeToCall;
                    return id;
                };
            }

            if (!window.cancelAnimationFrame) {
                window.cancelAnimationFrame = function (id) {
                    clearTimeout(id);
                };
            }
        };

        if (isCanvasSupported) {
            var c = document.getElementById('canvas');
            var cw = c.width = window.innerWidth;
            var ch = c.height = window.innerHeight;
            var cl = new Lightning(c, cw, ch);

            var init = function () {
                cl.clearCanvas();
                cl.update();
                cl.lightningTimer();
                cl.render();
                requestAnimationFrame(init, c);
            }

            setupRAF();
            init();
        }
    }
</script>
</html>